﻿/* 
 * 
 * Copyright (c) 2010 Photonic Games

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Graphics;

namespace GameLevel.ContentPipeline.Leadwerks14
{
    class Edge
    {
        public Vector3 Color1, Color2;
        public float X1, Y1, X2, Y2;
       

        public Edge(Vector3 color1, float x1, float y1, Vector3 color2, float x2, float y2)
        {
            if (y1 < y2)
            {
                Color1 = color1;
                X1 = x1;
                Y1 = y1;
                Color2 = color2;
                X2 = x2;
                Y2 = y2;
            }
            else
            {
                Color1 = color2;
                X1 = x2;
                Y1 = y2;
                Color2 = color1;
                X2 = x1;
                Y2 = y1;
            }
        }
    }


    class Span
    {

        public Vector3 Color1, Color2;
        public float X1, X2;

        public Span(Vector3 color1, float x1, Vector3 color2, float x2)
        {
            if (x1 < x2)
            {
                Color1 = color1;
                X1 = x1;
                Color2 = color2;
                X2 = x2;
            }
            else
            {
                Color1 = color2;
                X1 = x2;
                Color2 = color1;
                X2 = x1;
            }
        }
    }


    class Rasterizer
    {
        Leadwerks14LevelProcessor.VectorBuffer m_FrameBuffer;
        uint m_Width, m_Height;
        List<Leadwerks14LevelProcessor.LightEntity> m_lights;
        Vector3 m_Normal, m_Binormal, m_Tangent;

        const int NUM_BUMP_VECTS = 3;
        const float OO_SQRT_3 = 0.57735025882720947f;

        Vector3[] BumpBasisMatrix = new Vector3[]{
            new Vector3(0.81649661064147949f, 0.0f, OO_SQRT_3 ),
            new Vector3(-0.40824833512306213f, 0.70710676908493042f, OO_SQRT_3 ),
            new Vector3(-0.40824821591377258f, -0.7071068286895752f, OO_SQRT_3 )};
           

        Vector3[] basisVectors = new Vector3[NUM_BUMP_VECTS];
      
        public void SetFrameBuffer(Leadwerks14LevelProcessor.VectorBuffer frameBuffer,
            uint width, uint height)
        {
            m_FrameBuffer = frameBuffer;
            
            m_Width = width;
            m_Height = height;
        }

        public void SetTangents(Vector3 normal, Vector3 binormal, Vector3 tangent)
        {
            // Rotate vectors around Y
            Matrix matrix = Matrix.CreateFromAxisAngle(Vector3.Up, (float)Math.PI);
            m_Normal = normal;
            m_Normal.Z *= -1;

            m_Binormal = binormal;
            //m_Binormal.Z *= -1;

            m_Tangent = tangent;
            //m_Tangent.Z *= -1;


#if true
            Matrix world = new Matrix(m_Tangent.X, m_Tangent.Y, m_Tangent.Z, 0,
                m_Binormal.X, m_Binormal.Y, m_Binormal.Z, 0,
                m_Normal.X, m_Normal.Y, m_Normal.Z, 0,
                0, 0, 0, 1);
#else
            Matrix world = Matrix.CreateWorld(Vector3.Zero, m_Tangent, m_Normal);
#endif


            basisVectors[0] = Vector3.Transform(BumpBasisMatrix[0], world);
            basisVectors[1] = Vector3.Transform(BumpBasisMatrix[1], world);
            basisVectors[2] = Vector3.Transform(BumpBasisMatrix[2], world);

        }

        void SetPixel(uint x, uint y, Vector3 color)
        {
            if (x >= m_Width || y >= m_Height)
                return;

            m_FrameBuffer.buffer[y * m_Width + x] = color;

            int xo, yo;
            for (int i = 0; i < 9; i++)
            {
                switch (i)
                {
                    case 0: xo = -1; yo = -1; break;
                    case 1: xo = 0; yo = -1; break;
                    case 2: xo = 1; yo = -1; break;
                    case 3: xo = -1; yo = 0; break;
                    case 4: continue;
                    case 5: xo = 1; yo = 0; break;
                    case 6: xo = -1; yo = 1; break;
                    case 7: xo = 0; yo = 1; break;
                    case 8: xo = 1; yo = 1; break;
                    default: xo = 1; yo = 1; break;
                }

                // Set any unitialized pixels around this one.
                int offset = ((int)y + yo) * (int)m_Width + ((int)x + xo);
                if (m_FrameBuffer.buffer[offset] == Vector3.Zero) m_FrameBuffer.buffer[offset] = color;
            }
        }

        Vector3 findClosestLightDirection(Vector3 Position)
        {
            Vector3 lightPos = Vector3.Zero;
            Vector3 lightDir = Vector3.Zero;
           

            if (m_lights.Count < 1)
            {
                lightDir = Vector3.Zero;
                return lightDir;
            }

            float distance = 0, scale = 0;

            
            for (int i = 0; i < m_lights.Count; i++)
            {
                Vector3 direction = Vector3.Normalize(Position-m_lights[i].position);
                float dotProduct = Vector3.Dot(m_Normal,direction);
                
                // Don't use if the light is behind the object.
                
                //if (
                if ((dotProduct <= 0) && 
                  (Vector3.Distance(m_lights[i].position, Position) < m_lights[i].falloff))
                {
                    lightPos = m_lights[i].position;
                    distance = Vector3.Distance(lightPos, Position);
                    scale = (m_lights[i].falloff - distance) / (m_lights[i].falloff*2);
                    

                    lightDir.X += (float)Math.Max(Vector3.Dot(basisVectors[0], direction * scale * -1),0);
                    lightDir.Y += (float)Math.Max(Vector3.Dot(basisVectors[1], direction * scale * -1), 0);
                    lightDir.Z += (float)Math.Max(Vector3.Dot(basisVectors[2], direction * scale * -1), 0);

                    lightDir.X = (float) Math.Max(Math.Min(lightDir.X, 1), -1);
                    lightDir.Y = (float) Math.Max(Math.Min(lightDir.Y, 1), -1);
                    lightDir.Z = (float) Math.Max(Math.Min(lightDir.Z, 1), -1);
                   
                }
            }
            //lightDir.X = lightDir.Z = 0;
            // We only want how much light is coming from each direction. The amount comes from the lightmap
            if (lightDir == Vector3.Zero) lightDir = new Vector3(0, -1, 0);
            return Vector3.Normalize(lightDir);
        }
 
        void SetPixel(int x, int y, Vector3 color)
        {
            Vector3 lightDirection = findClosestLightDirection(color);
            //if (lightDirection == Vector3.Zero && m_Normal == Vector3.Forward) lightDirection = new Vector3(0, 0, -.2f);
            if (lightDirection == Vector3.Zero && m_Normal == Vector3.Backward) lightDirection = new Vector3(0, 0, 0.02f);
            SetPixel((uint)x, (uint)y, lightDirection);
        }

        void SetPixel(float x, float y, Vector3 color)
        {
            if (x < 0.0f || y < 0.0f)
                return;

            SetPixel((int)Math.Round(x), (int)Math.Round(y), color);
        }

        public void Clear()
        {
            m_FrameBuffer.buffer.Initialize();          
        }

        void DrawSpan(Span span, int y)
        {
            float xdiff = span.X2 - span.X1;
           
            Vector3 colordiff = span.Color2 - span.Color1;

            float factor = 0.0f;
            float factorStep = 1.0f / (float)xdiff;

            // draw each pixel in the span
            for (int x = (int)Math.Floor(span.X1); x <= (int)Math.Round(span.X2); x++)
            {
                SetPixel(x, y, span.Color1 + (colordiff * factor));
                factor += factorStep;
            }
        }

        void
        DrawSpansBetweenEdges(Edge e1, Edge e2)
        {
            // calculate difference between the y coordinates
            // of the first edge and return if 0
            float e1ydiff = (float)(e1.Y2 - e1.Y1);
           
            // calculate difference between the y coordinates
            // of the second edge and return if 0
            float e2ydiff = (float)(e2.Y2 - e2.Y1);
            
            // calculate differences between the x coordinates
            // and colors of the points of the edges
            float e1xdiff = (float)(e1.X2 - e1.X1);
            float e2xdiff = (float)(e2.X2 - e2.X1);
            Vector3 e1colordiff = (e1.Color2 - e1.Color1);
            Vector3 e2colordiff = (e2.Color2 - e2.Color1);

            // calculate factors to use for interpolation
            // with the edges and the step values to increase
            // them by after drawing each span
            float factor1 = (float)(e2.Y1 - e1.Y1) / e1ydiff;
            float factorStep1 = 1.0f / e1ydiff;
            float factor2 = 0.0f;
            float factorStep2 = 1.0f / e2ydiff;

            // loop through the lines between the edges and draw spans
            for (int y = (int)Math.Floor(e2.Y1); y <= (int)Math.Round(e2.Y2); y++)
            {
                // create and draw span
                Span span = new Span(e1.Color1 + (e1colordiff * factor1),
                          e1.X1 + (e1xdiff * factor1),
                          e2.Color1 + (e2colordiff * factor2),
                          e2.X1 + (e2xdiff * factor2));
                DrawSpan(span, y);

                // increase factors
                factor1 += factorStep1;
                factor2 += factorStep2;
            }
        }

        public void DrawTriangle(List<Leadwerks14LevelProcessor.LightEntity> lights, 
            Vector3 color1, float x1, float y1,
            Vector3 color2, float x2, float y2,                     
            Vector3 color3, float x3, float y3)
        {
            m_lights = lights;

            // create edges for the triangle
            Edge[] edges = new Edge[3];
            edges[0] = new Edge(color1, (int)Math.Round(x1), (int)Math.Round(y1),
                color2, (int)Math.Round(x2), (int)Math.Round(y2));
            edges[1] = new Edge(color2, (int)Math.Round(x2), (int)Math.Round(y2),
                color3, (int)Math.Round(x3), (int)Math.Round(y3));
            edges[2] = new Edge(color3, (int)Math.Round(x3), (int)Math.Round(y3),
                color1, (int)Math.Round(x1), (int)Math.Round(y1));


            int maxLength = 0;
            int longEdge = 0;

            // find edge with the greatest length in the y axis
            for (int i = 0; i < 3; i++)
            {
                int length = (int)Math.Round(edges[i].Y2 - edges[i].Y1);
                if (length > maxLength)
                {
                    maxLength = length;
                    longEdge = i;
                }
            }

            int shortEdge1 = (longEdge + 1) % 3;
            int shortEdge2 = (longEdge + 2) % 3;

            // draw spans between edges; the long edge can be drawn
            // with the shorter edges to draw the full triangle
            DrawSpansBetweenEdges(edges[longEdge], edges[shortEdge1]);
            DrawSpansBetweenEdges(edges[longEdge], edges[shortEdge2]);
        }

        public void
        DrawLine(Vector3 color1, float x1, float y1,
                              Vector3 color2, float x2, float y2)
        {
            float xdiff = (x2 - x1);
            float ydiff = (y2 - y1);

            if (xdiff == 0.0f && ydiff == 0.0f)
            {
                SetPixel(x1, y1, color1);
                return;
            }

            if (Math.Abs(xdiff) > Math.Abs(ydiff))
            {
                float xmin, xmax;

                // set xmin to the lower x value given
                // and xmax to the higher value
                if (x1 < x2)
                {
                    xmin = x1;
                    xmax = x2;
                }
                else
                {
                    xmin = x2;
                    xmax = x1;
                }

                // draw line in terms of y slope
                float slope = ydiff / xdiff;
                for (float x = xmin; x <= xmax; x += 1.0f)
                {
                    float y = y1 + ((x - x1) * slope);
                    Vector3 color = color1 + ((color2 - color1) * ((x - x1) / xdiff));
                    SetPixel(x, y, color);
                }
            }
            else
            {
                float ymin, ymax;

                // set ymin to the lower y value given
                // and ymax to the higher value
                if (y1 < y2)
                {
                    ymin = y1;
                    ymax = y2;
                }
                else
                {
                    ymin = y2;
                    ymax = y1;
                }

                // draw line in terms of x slope
                float slope = xdiff / ydiff;
                for (float y = ymin; y <= ymax; y += 1.0f)
                {
                    float x = x1 + ((y - y1) * slope);
                    Vector3 color = color1 + ((color2 - color1) * ((y - y1) / ydiff));
                    SetPixel(x, y, color);
                }
            }
        }

        public class Triangle
        {
            public uint[] index = new uint[] { 0, 0, 0 };
        }

        public void CalculateTangents(long vertexCount, BrushVertex[] vertex,
            long triangleCount, Triangle[] triangle)
        {
            Vector3[] tan1 = new Vector3[vertexCount];
            Vector3[] tan2 = new Vector3[vertexCount];

            for (long a = 0; a < triangleCount; a++)
            {
                long i1 = triangle[a].index[0];
                long i2 = triangle[a].index[1];
                long i3 = triangle[a].index[2];

                Vector3 v1 = vertex[i1].Position;
                Vector3 v2 = vertex[i2].Position;
                Vector3 v3 = vertex[i3].Position;

                Vector2 w1 = vertex[i1].TextureCoordinate;
                Vector2 w2 = vertex[i2].TextureCoordinate;
                Vector2 w3 = vertex[i3].TextureCoordinate;

                float x1 = v2.X - v1.X;
                float x2 = v3.X - v1.X;
                float y1 = v2.Y - v1.Y;
                float y2 = v3.Y - v1.Y;
                float z1 = v2.Z - v1.Z;
                float z2 = v3.Z - v1.Z;

                float s1 = w2.X - w1.X;
                float s2 = w3.X - w1.X;
                float t1 = w2.Y - w1.Y;
                float t2 = w3.Y - w1.Y;

                float r = 1.0F / (s1 * t2 - s2 * t1);
                Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
                        (t2 * z1 - t1 * z2) * r);
                Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
                        (s1 * z2 - s2 * z1) * r);

                tan1[i1] += sdir;
                tan1[i2] += sdir;
                tan1[i3] += sdir;

                tan2[i1] += tdir;
                tan2[i2] += tdir;
                tan2[i3] += tdir;
            }

            for (long a = 0; a < vertexCount; a++)
            {
                Vector3 n = vertex[a].Normal;
                Vector3 t = tan1[a];

                // Gram-Schmidt orthogonalize
                vertex[a].Tangent = Vector3.Normalize((t - n * Vector3.Dot(n, t)));
                vertex[a].Binormal = Vector3.Cross(vertex[a].Normal, vertex[a].Tangent);

                //    (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0F) ? -1.0F : 1.0F);

            }
        }
    }
}
